# 自动将项目根目录加入 sys.path，确保 utils 包可被正确导入

import sys
import os
import importlib.util
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 联动花花弹幕WS接收
from tools.huahua_ws_receiver import auto_start_huahua_ws_receiver, HuahuaWSReceiver
# 统一通过 config_api 读写配置
CONFIG_API_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'tools/config_api.py'))
spec = importlib.util.spec_from_file_location("config_api", CONFIG_API_PATH)
config_api = importlib.util.module_from_spec(spec)
sys.modules["config_api"] = config_api
spec.loader.exec_module(config_api)
from core.bilibili.client import BiliDanmuClient
from core.bilibili.manager import LiveRoomManager
from core.ai.engine import AIEngine
from core.danmaku.processor import DanmuProcessor
from core.danmaku.queue import TaskQueueManager
from core.ai.tts import TTSEngine
import pygame  # 仅保留音频播放功能
import asyncio
import os
import logging


global_danmu_sender = None
# 改为通过HTTP POST推送弹幕到webui_server
import requests
def add_danmu_to_cache(uname, msg, identity=None):
    # identity: dict, 可选，包含房管/舰长/粉丝牌等信息
    try:
        payload = {"uname": uname, "msg": msg}
        if identity:
            payload["identity"] = identity
        requests.post("http://127.0.0.1:8000/api/danmu/add", json=payload, timeout=1)
    except Exception as e:
        pass



# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    encoding='utf-8',
    force=True
)
logger = logging.getLogger("main")

logger.info('主程序已启动')

# 全局上下文
global_context = {}



# 新增：全局直播间管理器实例
global_room_manager = None

async def handle_danmaku(user_id, username, text):
    global global_danmu_sender
    global global_context
    global global_room_manager
    # 检查弹幕是否命中惩罚关键词，递进惩罚
    danmu_processor = global_context.get("danmu_processor")
    room_manager = global_room_manager
    if danmu_processor and room_manager:
        if user_id not in danmu_processor.user_punish_count:
            danmu_processor.user_punish_count = danmu_processor._load_punish_count()
        punish_level = danmu_processor.check_punish(user_id, text)
        if punish_level > 0:
            config = config_api.get_config()
            use_alt = config.get('bilibili', {}).get('use_alt_account', False)
            punish_reply = config.get('punish_reply', {})
            mute_reply = punish_reply.get('mute', '你因违反直播间规定被禁言，请注意言辞喵~')
            kick_reply = punish_reply.get('kick', '你多次违规，已被请出直播间并拉黑，文明发言喵！')
            mute_reply = f"@{username}({user_id}) {mute_reply}"
            kick_reply = f"@{username}({user_id}) {kick_reply}"
            from core.bilibili.danmaku_sender import DanmakuSender
            async def do_punish():
                global global_danmu_sender
                if punish_level == 1:
                    await room_manager.mute_user(user_id, minute=5)
                    logger.info(f"[自动禁言-1] {username}({user_id}) 第一次违规，禁言5分钟: {text}")
                    if global_danmu_sender is None:
                        global_danmu_sender = DanmakuSender(global_context["config"]["bilibili"]["room_id"])
                    await global_danmu_sender.send(mute_reply)
                elif punish_level == 2:
                    await room_manager.mute_user(user_id, minute=10)
                    logger.info(f"[自动禁言-2] {username}({user_id}) 第二次违规，禁言10分钟: {text}")
                    if global_danmu_sender is None:
                        global_danmu_sender = DanmakuSender(global_context["config"]["bilibili"]["room_id"])
                    await global_danmu_sender.send(mute_reply)
                elif punish_level >= 3:
                    await room_manager.kick_user(user_id)
                    logger.info(f"[自动拉黑踢出] {username}({user_id}) 第三次违规，已踢出并拉黑: {text}")
                    bl_path = os.path.join(os.path.dirname(__file__), 'config/blacklist_uid.txt')
                    try:
                        with open(bl_path, 'a', encoding='utf-8') as blf:
                            blf.write(f'{user_id},ban\n')
                    except Exception as e:
                        logger.warning(f"写入黑名单失败: {e}")
                    if global_danmu_sender is None:
                        global_danmu_sender = DanmakuSender(global_context["config"]["bilibili"]["room_id"])
                    await global_danmu_sender.send(kick_reply)
            if not use_alt:
                await do_punish()
                return
            else:
                try:
                    info = await room_manager.get_user_room_info(user_id)
                    is_admin = False
                    if info and 'user_info' in info and 'manager' in info['user_info']:
                        is_admin = info['user_info']['manager'] == 1
                    if is_admin:
                        await do_punish()
                    else:
                        global global_danmu_sender
                        if global_danmu_sender is None:
                            global_danmu_sender = DanmakuSender(global_context["config"]["bilibili"]["room_id"])
                        tip = "⚠️ 机器人无房管权限，无法自动禁言/拉黑！"
                        await global_danmu_sender.send(tip)
                        logger.info(f"[权限不足] 小号无房管权限，无法处理违禁弹幕: {text}")
                except Exception as e:
                    logger.warning(f"[自动禁言/拉黑] 小号权限检测失败: {e}")
                return
    # ...existing code...
    import re
    logger.info(f"[DEBUG] handle_danmaku 收到弹幕原文: {text}")

    # ====== 新增：自动识别观众身份 ======
    identity_info = None
    try:
        if global_room_manager is not None:
            identity_info = await global_room_manager.get_user_room_info(user_id)
    except Exception as e:
        logger.warning(f"获取观众身份信息失败: {e}")

    # 写入webui弹幕缓存，带身份
    try:
        add_danmu_to_cache(username, text, identity=identity_info)
    except Exception as e:
        logger.warning(f"写入webui弹幕缓存失败: {e}")

    # 统一提取“六月”及其后内容，忽略所有前缀
    keywords = global_context["config"].get("ai", {}).get("keywords", ["六月"])
    matched_kw = None
    def normalize(s):
        return re.sub(r'[\s\u3000\uFF01-\uFF5E\u3002\uff0c\u201c\u201d\u2018\u2019\u0020]+', '', s)
    text_content = text
    send_danmu = global_context["config"].get("tts", {}).get("send_danmu", True)
    from core.bilibili.danmaku_sender import DanmakuSender
    for kw in keywords:
        try:
            debug_str = f"[DEBUG] 当前唤醒词: {kw} | 弹幕: {repr(text)}"
            logger.debug(debug_str)
        except UnicodeEncodeError:
            try:
                print(debug_str.encode('gbk', errors='replace').decode('gbk', errors='replace'))
            except Exception:
                print("[DEBUG] 弹幕内容包含无法显示字符，已自动忽略部分内容")
        m = re.search(rf"(?:^|[\s:：，,]){re.escape(kw)}([\s\S]*)$", text)
        if m:
            matched_kw = kw
            content_raw = m.group(1)
            content_norm = normalize(content_raw)
            logger.debug(f"[DEBUG] 唤醒词: {kw} | content_raw: {repr(content_raw)} | content_norm: {repr(content_norm)}")
            if not content_norm:
                tts_enable = global_context["config"].get("tts", {}).get("enable", False)
                auto_reply = global_context["config"].get("ai", {}).get("auto_reply", "你好我叫六月是个猫娘，请问主人你有什么问题呢")
                tts_text = auto_reply
                logger.debug("[DEBUG] TTS分支被触发")
                logger.debug(f"[DEBUG] tts_text: {repr(tts_text)}")
                if tts_enable:
                    tts_engine = global_context["tts_engine"]
                    task = asyncio.create_task(tts_engine.generate_audio_and_play(tts_text))
                    logger.debug(f"[DEBUG] asyncio.create_task 返回: {task}")
                    logger.info(f"[TTS自定义回复] {tts_text}")
                if send_danmu:
                    if global_danmu_sender is None:
                        global_danmu_sender = DanmakuSender(global_context["config"]["bilibili"]["room_id"])
                    try:
                        asyncio.create_task(global_danmu_sender.send(tts_text))
                    except Exception as e:
                        logger.warning(f"自动弹幕发送失败: {e}")
                return
            else:
                content_to_ai = str(content_raw.strip())
                # 传递身份信息给后续处理（如需）
                if global_context["danmu_processor"].filter_danmaku(user_id, content_to_ai):
                    # 可扩展：将身份信息与任务一起传递
                    global_context["queue_manager"].add_task(user_id, content_to_ai)
                    if send_danmu:
                        if global_danmu_sender is None:
                            global_danmu_sender = DanmakuSender(global_context["config"]["bilibili"]["room_id"])
                        try:
                            pass
                        except Exception as e:
                            logger.warning(f"自动弹幕发送失败: {e}")
                else:
                    logger.debug(f"弹幕被过滤: {text}")
                return
        else:
            logger.debug(f"[DEBUG] 未匹配: {kw} | 弹幕: {repr(text)}")
    logger.debug(f"弹幕未触发AI回复: {text}")
    # 新增：TTS引擎支持直接生成并播放音频
    # 需在TTSEngine中实现generate_audio_and_play方法

async def process_danmaku_task(user_id, text):
    """处理弹幕任务的具体逻辑"""
    global global_context
    ai_engine = global_context["ai_engine"]
    tts_engine = global_context["tts_engine"]
    config = global_context["config"]



    try:
        # 获取AI文本回复
        text_response = await ai_engine.get_ai_response(text)
        logger.info(f"[AI回复] {text_response}")

        # 新增：AI回复自动发弹幕，支持分段
        send_danmu = config.get("tts", {}).get("send_danmu", True)
        use_huahua = config.get("huahua", {}).get("danmu_proxy_enable", False)
        if send_danmu:
            if use_huahua:
                # 花花弹幕接管，全部通过花花助手API发弹幕
                from tools.huahua_danmu_api import send_huahua_danmu
                danmu_limit = config.get("ai_danmu_limit", {})
                enable_limit = danmu_limit.get("enable", True)
                max_length = danmu_limit.get("max_length", 30)
                max_segments = danmu_limit.get("max_segments", 2)
                over_limit_tip = danmu_limit.get("over_limit_tip", "[AI回复过长，剩余内容未显示]")
                concise_mode = danmu_limit.get("concise_mode", False)
                reply_delay = danmu_limit.get("reply_delay", 3)
                def split_by_length_strict_no_tip(text, max_len, max_segs=None):
                    import re
                    result = []
                    curr = ''
                    count = 0
                    segs = 0
                    i = 0
                    while i < len(text):
                        ch = text[i]
                        if re.match(r'[\u4e00-\u9fa5]', ch):
                            add = 1
                        else:
                            add = 0.5
                        if count + add > max_len:
                            result.append(curr)
                            curr = ''
                            count = 0
                            segs += 1
                            if max_segs and segs >= max_segs:
                                break
                            continue
                        curr += ch
                        count += add
                        i += 1
                    if curr and (not max_segs or len(result) < max_segs):
                        result.append(curr)
                    return result
                clean_text = text_response.replace('…[剩余内容见下条]', '')
                import asyncio
                if reply_delay > 0:
                    await asyncio.sleep(reply_delay)
                def get_length(text):
                    import re
                    count = 0
                    for ch in text:
                        if re.match(r'[\u4e00-\u9fa5]', ch):
                            count += 1
                        else:
                            count += 0.5
                    return count
                if concise_mode:
                    # 精简模式下，AI回复超长直接发自定义提示
                    if get_length(clean_text) > max_length:
                        await send_huahua_danmu(str(over_limit_tip))
                    else:
                        await send_huahua_danmu(clean_text)
                elif enable_limit and max_length > 0:
                    segs = split_by_length_strict_no_tip(clean_text, max_length, max_segments)
                    for seg in segs:
                        await send_huahua_danmu(seg)
                    # 如果被截断，补发提示
                    joined = ''.join(segs)
                    if max_segments and len(segs) == max_segments and len(joined) < len(clean_text):
                        await send_huahua_danmu(str(over_limit_tip))
                else:
                    # 未开启限制时，若AI回复超出B站最大弹幕长度，也只发自定义提示
                    max_len = max_length if max_length > 0 else 30
                    if get_length(clean_text) > max_len:
                        await send_huahua_danmu(str(over_limit_tip))
                    else:
                        await send_huahua_danmu(text_response)
            else:
                global global_danmu_sender
                if global_danmu_sender is None:
                    from core.bilibili.danmaku_sender import DanmakuSender
                    global_danmu_sender = DanmakuSender(config["bilibili"]["room_id"])
                try:
                    # 读取弹幕长度限制配置
                    danmu_limit = config.get("ai_danmu_limit", {})
                    enable_limit = danmu_limit.get("enable", True)
                    max_length = danmu_limit.get("max_length", 30)
                    max_segments = danmu_limit.get("max_segments", 2)
                    over_limit_tip = danmu_limit.get("over_limit_tip", "[AI回复过长，剩余内容未显示]")
                    concise_mode = danmu_limit.get("concise_mode", False)
                    reply_delay = danmu_limit.get("reply_delay", 3)
                    def split_by_length_strict(text, max_len, max_segs=None, tip='…[剩余内容见下条]'):
                        import re
                        result = []
                        curr = ''
                        count = 0
                        segs = 0
                        i = 0
                        while i < len(text):
                            ch = text[i]
                            # 汉字算1，其他算0.5
                            if re.match(r'[\u4e00-\u9fa5]', ch):
                                add = 1
                            else:
                                add = 0.5
                            # 判断加上提示后是否超长（仅非最后一段）
                            is_last = False
                            if max_segs and segs == max_segs - 1:
                                is_last = True
                            if not is_last and count + add + len(tip) > max_len:
                                # 当前段已满，且加提示也会超长，先补提示
                                # 若curr已空，至少塞一个字符
                                if not curr:
                                    curr = ch
                                    i += 1
                                curr = curr.rstrip()
                                # 截断提示也要保证不超长
                                remain = max_len - count
                                tip_to_add = tip[:int(remain)] if remain < len(tip) else tip
                                curr += tip_to_add
                                result.append(curr)
                                curr = ''
                                count = 0
                                segs += 1
                                if max_segs and segs >= max_segs:
                                    break
                                continue
                            curr += ch
                            count += add
                            i += 1
                            if not is_last and count + len(tip) >= max_len:
                                curr = curr.rstrip()
                                remain = max_len - count
                                tip_to_add = tip[:int(remain)] if remain < len(tip) else tip
                                curr += tip_to_add
                                result.append(curr)
                                curr = ''
                                count = 0
                                segs += 1
                                if max_segs and segs >= max_segs:
                                    break
                        if curr and (not max_segs or len(result) < max_segs):
                            result.append(curr)
                        return result
                    def split_by_length_strict_no_tip(text, max_len, max_segs=None):
                        import re
                        result = []
                        curr = ''
                        count = 0
                        segs = 0
                        i = 0
                        while i < len(text):
                            ch = text[i]
                            if re.match(r'[\u4e00-\u9fa5]', ch):
                                add = 1
                            else:
                                add = 0.5
                            if count + add > max_len:
                                result.append(curr)
                                curr = ''
                                count = 0
                                segs += 1
                                if max_segs and segs >= max_segs:
                                    break
                                continue
                            curr += ch
                            count += add
                            i += 1
                        if curr and (not max_segs or len(result) < max_segs):
                            result.append(curr)
                        return result
                    clean_text = text_response.replace('…[剩余内容见下条]', '')
                    import asyncio
                    if reply_delay > 0:
                        await asyncio.sleep(reply_delay)
                    def get_length(text):
                        import re
                        count = 0
                        for ch in text:
                            if re.match(r'[\u4e00-\u9fa5]', ch):
                                count += 1
                            else:
                                count += 0.5
                        return count
                    if concise_mode:
                        # 精简模式下，AI回复超长直接发自定义提示
                        if get_length(clean_text) > max_length:
                            await global_danmu_sender.send(str(over_limit_tip))
                        else:
                            await global_danmu_sender.send(clean_text)
                    elif enable_limit and max_length > 0:
                        segs = split_by_length_strict_no_tip(clean_text, max_length, max_segments)
                        for seg in segs:
                            await global_danmu_sender.send(seg)
                        # 如果被截断，补发提示
                        joined = ''.join(segs)
                        if max_segments and len(segs) == max_segments and len(joined) < len(clean_text):
                            await global_danmu_sender.send(str(over_limit_tip))
                    else:
                        # 未开启限制时，若AI回复超出B站最大弹幕长度，也只发自定义提示
                        max_len = max_length if max_length > 0 else 30
                        if get_length(clean_text) > max_len:
                            await global_danmu_sender.send(str(over_limit_tip))
                        else:
                            await global_danmu_sender.send(text_response)
                except Exception as e:
                    logger.warning(f"AI弹幕自动发送失败: {e}")

        # 仅在TTS开启时才生成语音
        tts_enable = config.get("tts", {}).get("enable", False)
        if tts_enable:
            audio_data, audio_path = await tts_engine.generate_audio(text_response)
            if audio_data is not None and os.path.exists(audio_path):
                # 播放音频
                pygame.mixer.Sound(audio_path).play()
            else:
                logger.warning("语音生成失败，无法播放音频")
        else:
            logger.info("TTS已关闭，不生成语音")

    except Exception as e:
        logger.error(f"处理弹幕任务失败: {e}", exc_info=True)


async def main():
    import os
    logger.info(f'唯一启动标识: {os.getpid()}')
    import threading
    import sys
    import importlib.util
    CONFIG_API_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'tools/config_api.py'))
    spec = importlib.util.spec_from_file_location("config_api", CONFIG_API_PATH)
    config_api = importlib.util.module_from_spec(spec)
    sys.modules["config_api"] = config_api
    spec.loader.exec_module(config_api)
    global global_room_manager
    # 加载配置
    try:
        config = config_api.get_config()
        logger.info("配置加载成功")
    except Exception as e:
        logger.error(f"配置加载失败: {e}")
        return

    # 启动后台线程定时热加载配置
    def config_hot_reload_worker():
        import time
        while True:
            try:
                new_config = config_api.get_config()
                global global_context
                if global_context and "config" in global_context:
                    global_context["config"] = new_config
                time.sleep(30)
            except Exception as e:
                logger.warning(f"[热加载] 配置热加载异常: {e}")
                time.sleep(30)
    threading.Thread(target=config_hot_reload_worker, daemon=True).start()

    # 初始化Pygame音频系统
    pygame.mixer.init()

    # 获取当前事件循环
    loop = asyncio.get_running_loop()

    try:
        # 新增：初始化直播间管理器（身份识别）
        global global_room_manager
        global_room_manager = LiveRoomManager(config["bilibili"]["room_id"])

        # AI引擎
        ai_engine = AIEngine(config)

        # 弹幕处理器
        danmu_processor = DanmuProcessor()

        # 任务队列（传入事件循环）
        queue_manager = TaskQueueManager(config, loop=loop)
        queue_manager.set_task_handler(process_danmaku_task)

        # TTS引擎
        tts_engine = TTSEngine(config)

        # 全局上下文
        global global_context
        global_context = {
            "ai_engine": ai_engine,
            "danmu_processor": danmu_processor,
            "queue_manager": queue_manager,
            "tts_engine": tts_engine,
            "config": config
        }

        # 启动队列处理
        queue_manager.start()

        # 判断是否只用花花WS接收弹幕
        use_huahua_ws = config.get("huahua", {}).get("danmu_receive_enable", False)
        if not use_huahua_ws:
            # 启动B站弹幕监听
            danmu_client = BiliDanmuClient(
                config["bilibili"]["room_id"],
                message_callback=handle_danmaku
            )
            await danmu_client.start_listening()

        # 保持程序运行
        try:
            while True:
                await asyncio.sleep(1)
        except KeyboardInterrupt:
            logger.info("程序被用户终止")
        finally:
            if not use_huahua_ws:
                await danmu_client.stop_listening()
            queue_manager.shutdown()
            pygame.mixer.quit()
            logger.info("资源清理完成")

    except Exception as e:
        logger.error(f"程序运行失败: {e}", exc_info=True)

def handle_huahua_danmu(data):
    # data: dict, 结构参考花花WS推送格式
    # 典型结构: {'uname': 'xxx', 'uid': 123, 'text': '弹幕内容', ...}
    import asyncio
    from threading import Thread
    # 只处理文本弹幕
    text = None
    user_id = None
    username = None
    if isinstance(data, dict):
        text = data.get('text')
        user_id = data.get('uid')
        username = data.get('uname')
    if text and user_id and username:
        # 违禁词检测和AI问答复用主流程逻辑
        loop = None
        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            loop = None
        async def process():
            await handle_danmaku(user_id, username, text)
        if loop and loop.is_running():
            asyncio.run_coroutine_threadsafe(process(), loop)
        else:
            asyncio.run(process())

if __name__ == "__main__":
    # 启动花花弹幕WS接收（如配置开启），并注册回调
    from tools.huahua_ws_receiver import huahua_ws_receiver
    if huahua_ws_receiver is not None:
        huahua_ws_receiver.on_danmu = handle_huahua_danmu
    else:
        # 若未自动启动，则手动启动并注册回调
        ws = HuahuaWSReceiver(on_danmu=handle_huahua_danmu)
        ws.start()
    # 启动主流程
    asyncio.run(main())